/**
 * @brief This is a test program in C language.
 * m目的：创建一个socket 用于联系C与Cpp之间的通信
 *
 * 明确网络通信和普通进程间通信在确定对手唯一ID方面的差池：
 * 本地通信：通过pid确定对手件的唯一ID.
 * 网络通信：通过一个三元组确定对手的唯一ID：
 * IP地址：确认网络中设备位置，
 * 端口号：确认设备中是哪个进程在跟自己通信，
 * 协议类型：解析來自两个设备间数据包内容
 * 因此我们玩网络socket编程时，需要确定对手的IP地址、端口号、协议类型，才能进行通信。
 * 三要素通过sockaddr_in结构体表示：
 * struct sockaddr_in {
 *     short   sin_family; // 协议族，AF_INET表示IPv4
 *     uint16_t sin_port; // 端口号
 *     struct  in_addr sin_addr; // IP地址
 *     char    sin_zero[8]; // 填充字节
 * };
 * 其中，sin_family表示协议族，sin_port表示端口号，sin_addr表示IP地址，sin_zero表示填充字节。
 * ip地址对应代码中的：

 * 端口号对应代码中的：

 * 协议类型对应代码中的：
 思路持续推进，有了三要素后，就通过绑定，发送，接收来处理数据即可。

 AF_INET：address family，表示协议族，IPv4
 SOCK_DGRAM：socket datagram，表示数据报协议
 ** 问题：数据报协议和字节流协议之间如何用人话理解？
 答：字节流有很多特点，当谈论到二者区别，应当忽略网络上各种答案，认真分析数据在传输时为什么非要分成流和包，各有什么优点。

背景：高考英语对一段话排序的题还记得吧，假设老师现在把这套题拆成7张试卷，隔空扔给你，你要把这7段话恢复原样。
场景1：你完全拿到了7个自然段，且乱序的，但是由于你是学霸，能将其恢复原样，这就是一次完美的，没有丢包的基于流的传输。
场景2：老师准度不太好，你只拿到了3个自然段，那你就得让监考老师补发试卷了，你再将其恢复原样，这就是一次不完美的，有丢包，和冲传的基于流的传输。

场景3：你是个学渣，没有那个本事把乱序的题重新组合，你每次收到一张，都一脸懵逼的不理解题目意思，头脑简单的你只能按照先后收到卷子的顺序给它们排序，然后挂科。。这就是有乱序后，基于包的传输。
场景4：你是个学渣，没有那个本事把乱序的题重新组合，你每次收到一张，都一脸懵逼的不理解题目意思，头脑简单的你只能按照先后收到卷子的顺序给它们排序，但是今天很欧皇，你每次收到的7张卷子顺序竟然都是正确的然后挂科。。这就是没乱序后，基于包的传输。
场景5：你是个学渣兼倒霉蛋，老师就给你发了2张卷子，那你就得让监考老师补发试卷了，监考老师压根就没想过，也没看到卷子丢了，于是你必然挂科啦。这就是丢包状态下的基于包的传输。
总结：1：基于流的传输有在缓存里重新根据约定排序的能力。学霸具备重排能力，即自带缓存能力，将乱序的题目重新组合好交卷给用户。
      2：基于包的传输没有缓存，只能按照先后收到卷子的顺序给它们排序，如果中间有丢包，则会出现错误，如果出现乱序，则会出现错误，要么就得用户自己处理。
以上便是我对8年前学堂里学到的tcp  udp之间官方术语的通俗理解。
1.数据包排序上。
2.tcp具备自主拆散包能力，将一个超过mtu的tcp segment拆成两个tcp包。接收端会将两个包组装成一个完整的tcp segment。
  htons：host to network short，主机到网络短整数转换函数
3.流是全自动发送，内部会用他自己的优化策略来发送数据，最终一定能完整送达。数据包则是半自动需要用户自己控制。
4。数据包加数据发送算法=流传输。
5.udp采用的数据包是原始报文包，tcp采用的流传输是将这写数据包加以算法利用起来。如果对tcp不满意,可以基于udp自己做一个Xdp出来。
6.udp理解成socket,tcp理解成基于socket的zmq框架。tcp就像是基于某种厉害的原理，封装了一系列框架的完成品。
 inet_addr：IP地址转换函数

 ** 问题：为什么用一个int类型的socket变量可以确定一个套接字？，该整形变量是如何让OS知道这个套接字的唯一性的呢？，系统发送数据时，如何在进程和内核中利用这个整形做收发呢？
 答：socket变量本身就是一个整型变量，它的值就是操作系统分配给这个套接字的唯一标识符。操作系统会在内核中维护一个套接字表，用来记录所有打开的套接字，并通过这个表来确定套接字的唯一性。
所有说真正读写socket文件的只有内核对吗？
是的，真正读写socket文件的只有内核。用户进程通过文件描述符与内核进行交互，而内核负责管理所有的文件和套接字，包括读写操作

具体来说，当用户进程调用sendto或recvfrom等函数时，这些函数会触发系统调用，将控制权转移到内核。内核根据文件描述符找到对应的套接字，并进行相应的读写操作
问：能详细说一下从调用sendto函数到内核发送数据包的整个过程吗？
答：1.首先，系统调用sendto会将用户进程的缓冲区中的数据拷贝到内核的缓冲区中，并将控制权转移到内核。
1.1：调用sendto函数时，会触发一次跟udp发送相关的软件中断，再该中断中，用户态被切换为内核态，有资格去执行调用网口硬件发送数据包的能力，只有再次作用域内，内核会调用tcpip协议栈，并亲自对socket文件读写，操作硬件发送。
2.然后，内核会根据套接字的类型和地址，将数据包发送到对端。
3.最后，内核会将数据包从内核缓冲区拷贝到网卡缓冲区，并将控制权转移到网卡驱动程序。
4.网卡驱动程序会将数据包发送到网络，并将控制权转移到网络协议栈（From内核，注意不是网卡驱动在做流控）。
5.网络协议栈会将数据包封装成网络数据包，并将控制权转移到网络层。
6.网络层会将数据包添加上IP头部和TCP头部，并将控制权转移到IP协议栈。
7.IP协议栈会将数据包封装成IP数据包，并将控制权转移到传输层。
8.传输层会将数据包添加上TCP头部，并将控制权转移到TCP协议栈。
9.TCP协议栈会将数据包封装成TCP数据包，并将控制权转移到应用层。
10.应用层会将数据包从TCP协议栈中取出，并将控制权转移到用户进程。
11.用户进程会将数据包从内核缓冲区拷贝到用户缓冲区，并返回。

 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include "public.h"
#include <errno.h>

int main()
{
    // step1:
    // 确定对手的IP地址、端口号、协议类型
    struct sockaddr_in addr_send;
    addr_send.sin_family = AF_INET; // IPv4
    addr_send.sin_port = htons(CLIENT_SEND_PORT);
    addr_send.sin_addr.s_addr = inet_addr("127.0.0.1");
    // step2:
    // 创建套接字
    int sockfd_send;
    if ((sockfd_send = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("Create socket error\n");
        exit(1);
    }

    // step4:
    // 发送数据
    char send_buf[] = "Hello, world!";
    if (sendto(sockfd_send, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_send, sizeof(addr_send)) == -1)
    {
        printf("Send data error\n");
        exit(1);
    }

    // step3:
    // 绑定套接字用于监听发送到自己这边的消息
    struct sockaddr_in addr_listen;
    addr_listen.sin_family = AF_INET; // IPv4
    addr_listen.sin_port = htons(CLIENT_LISTEN_PORT);
    addr_listen.sin_addr.s_addr = inet_addr("127.0.0.1");
    int sockfd_listen = 0;
    if ((sockfd_listen = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        printf("Create sockfd_listen error\n");
        exit(1);
    }

    if (bind(sockfd_listen, (struct sockaddr *)&addr_listen, sizeof(addr_listen)) == -1)
    {
        printf("Bind socket error\n");
        if (errno == EADDRINUSE) 
        {
            printf("Port 8083 is already in use\n");
        } 
        else 
        {
            perror("bind");
        }
        exit(1);
    }

    // step5:
    // 接收数据
    char recv_buf[1024];
    socklen_t len = sizeof(addr_listen);
    if (recvfrom(sockfd_listen, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_listen, &len) == -1)
    {
        printf("Recv data error\n");
        exit(1);
    }
    printf("Recv data: %s\n", recv_buf);
    // step6:
    // 关闭套接字
    close(sockfd_listen);
    close(sockfd_send);
    return 0;
}